package com.geekydoo.commands;

import generated.TaskType;

import java.awt.Desktop;
import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.List;
import java.util.ListIterator;
import java.util.logging.Logger;

import com.geekydoo.InputOutputHandler;
import com.geekydoo.InputValidation;

/**
 * The purpose of this class is to support opening a task's document links.
 * 
 * @author Wai Yau, Patrick Dutch
 */
public class OpenCommand implements Command {

	private static Logger logger = Logger.getLogger("geekydoo");

	// The list of all task objects managed by the program
	private List<TaskType> taskList;

	// Used to open document links
	private Desktop desktop = Desktop.getDesktop();

	// Prompts printed by the open command
	private static final String PROMPT_ID = "Task ID: ";
	private static final String PROMPT_OPEN_LINKS =
			"Which link to open? (blank to cancel and 'all' to open all links): ";

	// Information messages
	private static final String INFO_EMPTY_LIST = "Task list is empty.";
	private static final String INFO_NOTHING_TO_OPEN = "Nothing to open.";
	private static final String INFO_NOT_OPEN_LINKS = "Not opening any document links.";
	private static final String INFO_SHOW_TASK = "Here's a list of your links:";

	// Error messages
	private static final String ERROR_INVALID_ID = "Invalid task id.";
	private static final String ERROR_FILE_OPEN_FAILED = "The file could not be opened.";
	private static final String ERROR_WEB_LINK_NOT_WELL_FORMED = "Webpage link is not well formed.";
	private static final String ERROR_UNKNOWN_PROB_WEB_LINK = "Unknown problem with opening webpage.";
	private static final String ERROR_WEB_LINK_SYNTAX = "Problem with the syntax of the URL";
	private static final String ERROR_NO_EMAIL_AGENT = "You don't have a default mail client set up.";
	private static final String ERROR_INVALID_DOCUMENT_NAME = "Document name is invalid.";

	// User inputs having special meaning to the command
	private static final String EMPTY_STRING = "";
	private static final String ALL_STRING = "((?i)all)|((?i)every)";

	// Regular expression for matching links
	private static final String REGEX_EMAIL = ".+@.+\\.[a-z]+";
	private static final String REGEX_HEADER_WEBLINK = "https?://";

	// Prefixes needed for opening links
	private static final String PREFIX_WEBLINK_NORMAL = "http";
	private static final String PREFIX_WEBLINK_JAVA = "http://:/";
	private static final String PREFIX_MAIL_TO = "mailto:";

	public void execute(List<TaskType> tasks) {
		taskList = tasks;
		if (taskList.size() == 0) {
			logger.info("Task list is empty so can't open anything");
			InputOutputHandler.printToConsole(INFO_EMPTY_LIST, true, false);
			return;
		}

		String id = InputOutputHandler.getInputFromConsole(PROMPT_ID, true);
		if (InputValidation.isValidId(id, taskList)) {
			processResponse(id);
		}
		else {
			InputOutputHandler.printToConsole(ERROR_INVALID_ID, true, false);
		}
	}

	/**
	 * This method determines which links the user wants to open and then opens
	 * them.
	 * 
	 * @param id The ID of the task whose links we would like to open
	 */
	private void processResponse(String id) {
		TaskType task = getTaskById(id);
		List<String> uriList = task.getDocumentLinks();

		if (uriList.size() == 0) {
			logger.info("The task doesn't have any document links");
			InputOutputHandler.printToConsole(INFO_NOTHING_TO_OPEN, true, false);
			return;
		}

		InputOutputHandler.printToConsole(INFO_SHOW_TASK, true, false);
		int index = 0;
		ListIterator<String> uriIterator = uriList.listIterator();
		while (uriIterator.hasNext()) {
			index++;
			InputOutputHandler.printToConsole(" " + index + ") " + uriIterator.next(), true, true);
		}

		String itemNo = EMPTY_STRING;
		while (!InputValidation.isValidLinkNumber(itemNo, index)) {
			itemNo = InputOutputHandler.getInputFromConsole(PROMPT_OPEN_LINKS, true);
			if (itemNo.equals(EMPTY_STRING) || itemNo.matches(ALL_STRING)) {
				break;
			}
		}
		if (itemNo.equals(EMPTY_STRING)) {
			logger.info("No link was opened since user input was blank");
			InputOutputHandler.printToConsole(INFO_NOT_OPEN_LINKS, true, false);
		}
		else if (itemNo.matches(ALL_STRING)) {
			uriIterator = uriList.listIterator();
			String uri;
			while (uriIterator.hasNext()) {
				uri = uriIterator.next();
				openDocument(uri);
			}
			logger.info("User chose to open all links");
		}
		else {
			String uri;
			uri = uriList.get(Integer.parseInt(itemNo) - 1);
			openDocument(uri);
		}
	}

	/**
	 * This method opens the document link.
	 * 
	 * @param uri The link to be opened
	 */
	private void openDocument(String uri) {
		logger.info("The user wants to open URI: " + uri);

		// An address beginning with http:// or https:// is not considered to be
		// well-formed by the method used to check URLs below, so we need to
		// remove this portion. We also need to remove anything after the TLD,
		// for example, in google.com/default.html we need to remove the
		// /default.html part before checking whether the address is well
		// formed.
		if (uri.startsWith(PREFIX_WEBLINK_NORMAL)) {
			uri = uri.replaceAll(REGEX_HEADER_WEBLINK, "");
		}
		String uriHost = EMPTY_STRING;
		if (uri.contains("/")) {
			int slashIndex = uri.indexOf("/");
			uriHost = uri.substring(0, slashIndex);
		}
		else {
			uriHost = uri;
		}

		File fileName = new File(uri);
		logger.info("The URI we're checking for wellformedness is: " + uriHost);
		if (com.sun.org.apache.xerces.internal.util.URI.isWellFormedAddress(uriHost)) {
			openWebpage(uri);
		}
		else if (uri.matches(REGEX_EMAIL)) {
			openEmail(uri);
		}
		else if (fileName.exists()) {
			openFile(fileName);
		}
		else {
			logger.info("Document link is invalid. Link is: " + uri);
			InputOutputHandler.printToConsole(ERROR_INVALID_DOCUMENT_NAME, true, false);
		}

	}

	/**
	 * This method opens web links in the browser.
	 * 
	 * @param uri The web link to be opened
	 */
	private void openWebpage(String uri) {
		try {
			URL websiteAddress = new URL(PREFIX_WEBLINK_JAVA + uri);
			desktop.browse(websiteAddress.toURI());
			logger.info("Opening webpage...");
		}
		catch (MalformedURLException e) {
			logger.info("URL is not well formed. URL is: " + uri);
			InputOutputHandler.printToConsole(ERROR_WEB_LINK_NOT_WELL_FORMED, true, false);
		}
		catch (IOException e) {
			logger.info("Unknown problem with I/O when opening web page. URL is: " + uri);
			InputOutputHandler.printToConsole(ERROR_UNKNOWN_PROB_WEB_LINK, true, false);
		}
		catch (URISyntaxException e) {
			logger.info("URI syntax error. URL is: " + uri);
			InputOutputHandler.printToConsole(ERROR_WEB_LINK_SYNTAX, true, false);
		}
	}

	/**
	 * This method opens the default mail client with the uri argument as the
	 * "to" field.
	 * 
	 * @param uri The email address mail should be sent to
	 */
	private void openEmail(String uri) {
		java.net.URI email = java.net.URI.create(PREFIX_MAIL_TO + uri);
		try {
			desktop.mail(email);
			logger.info("Mail application is opened.");
		}
		catch (IOException e) {
			logger.info("Can't open a default mail client.");
			InputOutputHandler.printToConsole(ERROR_NO_EMAIL_AGENT, true, false);
		}
	}

	/**
	 * This method opens files on the local computer using the default handler
	 * for the file type.
	 * 
	 * @param fileName The file to be opened
	 */
	private void openFile(File fileName) {
		try {
			desktop.open(fileName);
			logger.info("File is opened");
		}
		catch (IOException e) {
			logger.info("Unable to open the file " + fileName.getAbsolutePath());
			InputOutputHandler.printToConsole(ERROR_FILE_OPEN_FAILED, true, false);
		}
	}

	/**
	 * This method returns the task with ID matching the id argument.
	 * 
	 * @param id The ID of the task that should be returned
	 * @return the task with ID matching the id argument
	 */
	private TaskType getTaskById(String id) {
		TaskType theTask = null;
		for (int i = 0; i < taskList.size(); i++) {
			if (id.equals(taskList.get(i).getId())) {
				theTask = taskList.get(i);
			}
		}
		return theTask;
	}

	public boolean isListModified() {
		return false;
	}

	public List<TaskType> getPreExecutionList() {
		return taskList;
	}

}
